package top.codef.sqlfilter;

import java.lang.reflect.Field;
import java.util.stream.Stream;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import top.codef.exceptions.JpaAmebaException;
import top.codef.sqlfilter.annotations.GroupByCondition;
import top.codef.sqlfilter.annotations.HavingCondition;
import top.codef.sqlfilter.annotations.JoinCondition;
import top.codef.sqlfilter.annotations.Limit;
import top.codef.sqlfilter.annotations.SimpleJoin;
import top.codef.sqlfilter.annotations.SimpleOr;
import top.codef.sqlfilter.annotations.SubQuery;
import top.codef.sqlfilter.annotations.WhereCondition;
import top.codef.sqlfilter.functional.GlobalSupplier;
import top.codef.sqlfilter.typed.TypedCommonFilter;

public class QueryBuilder {

	private static final Log logger = LogFactory.getLog(QueryBuilder.class);

	public static CommonFilter createFilter() {
		return new CommonFilter();
	}

	public static CommonFilter createFilter(GlobalSupplier supplier) {
		return supplier == null ? createFilter() : supplier.get();
	}

	public static <R, T> SubCommonFilter<R, T> subQuery(Class<R> root, Class<T> tar) {
		return new SubCommonFilter<R, T>(root, tar);
	}

	public static JoinCommonFilter join(String joinField) {
		return new JoinCommonFilter(joinField);
	}

	public static <T> TypedCommonFilter<T> typeFilter(Class<T> clazz) {
		return new TypedCommonFilter<>(clazz);
	}

	public static HavingCommonFilter having() {
		return new HavingCommonFilter();
	}

	public static <F extends AbstractFilter<F>, T> F forQuery(F filter, T queryObj) {
		if (queryObj == null)
			return filter;
		var clazz = queryObj.getClass();
		final var fields = FieldUtils.getAllFields(clazz);
		Stream.of(fields).filter(x -> x.isAnnotationPresent(JoinCondition.class)).forEach(x -> {
			var joinCondition = x.getDeclaredAnnotation(JoinCondition.class);
			forJoinQuery(filter, x, joinCondition, queryObj);
		});
		Stream.of(fields).filter(x -> x.isAnnotationPresent(GroupByCondition.class)).forEach(x -> {
			var groupByCondition = x.getDeclaredAnnotation(GroupByCondition.class);
			groupby(filter, x, groupByCondition, queryObj);
		});
		Stream.of(fields).filter(x -> x.isAnnotationPresent(SimpleOr.class)).forEach(x -> {
			var orCondition = x.getDeclaredAnnotation(SimpleOr.class);
			or(filter, x, orCondition, queryObj);
		});
		limit(filter, fields, queryObj);
		Stream.of(fields).filter(x -> x.isAnnotationPresent(WhereCondition.class)).forEach(x -> {
			var fieldName = x.getName();
			var val = getFieldVal(x, queryObj, Object.class);
			if (val == null)
				return;
			var condition = x.getDeclaredAnnotation(WhereCondition.class);
			fieldName = StringUtils.isBlank(condition.value()) ? fieldName : condition.value();
			var subQuery = x.getDeclaredAnnotation(SubQuery.class);
			if (subQuery != null) {
				var subFilter = filter.subQ(subQuery.root(), subQuery.target(), fieldName).select(subQuery.value());
				val = forQuery(subFilter, val);
				filter.eq(fieldName, val);
				return;
			}
			var simpleJoin = x.getDeclaredAnnotation(SimpleJoin.class);
			if (simpleJoin != null) {
				var joinField = simpleJoin.value();
				var joinFilter = QueryBuilder.join(joinField);
				condition.condition().getConditionFilter().filter(joinFilter, fieldName, val);
				filter.join(joinField, joinFilter, simpleJoin.joinType());
				return;
			}
			var filterSy = condition.condition();
			filterSy.getConditionFilter().filter(filter, fieldName, val);
			return;
		});
		return filter;
	}

	private static <T> AbstractFilter<?> or(AbstractFilter<?> filter, Field field, SimpleOr orCondition, T queryObj) {
		var val = getFieldVal(field, queryObj, Object.class);
		if (val == null)
			return filter;
		var orfilter = new CommonFilter();
		Stream.of(orCondition.conditions()).forEach(x -> forNormalQuery(orfilter, field, x, queryObj));
		filter.or(orfilter);
		return filter;
	}

	public static <T> CommonFilter forQuery(T queryObj) {
		return forQuery(createFilter(), queryObj);
	}

	public static <T> CommonFilter forQuery(GlobalSupplier globalSupplier, T queryObj) {
		return forQuery(createFilter(globalSupplier), queryObj);
	}

	private static <T> AbstractFilter<?> groupby(AbstractFilter<?> parentFilter, Field field,
			GroupByCondition groupByCondition, T queryObj) {
		var val = getFieldVal(field, queryObj, Object.class);
		if (val != null) {
			parentFilter.groupBy(groupByCondition.value());
			if (groupByCondition.having()) {
				var havingfilter = QueryBuilder.having();
				parentFilter.having(havingfilter);
				FieldUtils.getFieldsListWithAnnotation(val.getClass(), HavingCondition.class).forEach(x -> {
					var havingCondition = x.getDeclaredAnnotation(HavingCondition.class);
					forHavingQuery(havingfilter, x, havingCondition, val);
				});
				FieldUtils.getFieldsListWithAnnotation(val.getClass(), WhereCondition.class).forEach(x -> {
					WhereCondition where = x.getDeclaredAnnotation(WhereCondition.class);
					forNormalQuery(havingfilter, x, where, val);
				});
			}
		}
		return parentFilter;
	}

	private static <T> HavingCommonFilter forHavingQuery(HavingCommonFilter havingCommonFilter, Field field,
			HavingCondition havingCondition, T queryObj) {
		var val = getFieldVal(field, queryObj, Object.class);
		var fieldName = StringUtils.isBlank(havingCondition.value()) ? field.getName() : havingCondition.value();
		if (val != null)
			havingCondition.condition().getConditionFilter().filter(havingCommonFilter,
					havingCondition.function().getSelect().apply(fieldName), val);
		return havingCommonFilter;
	}

	private static <T> AbstractFilter<?> forNormalQuery(AbstractFilter<?> filter, Field field,
			WhereCondition whereCondition, T queryObj) {
		var val = getFieldVal(field, queryObj, Object.class);
		var fieldName = StringUtils.isBlank(whereCondition.value()) ? field.getName() : whereCondition.value();
		if (val != null)
			whereCondition.condition().getConditionFilter().filter(filter, fieldName, val);
		return filter;
	}

	private static <T> JoinCommonFilter forJoinQuery(AbstractFilter<?> filter, Field field, JoinCondition joinCondition,
			T queryObj) {
		field.setAccessible(true);
		var joinField = field.getName();
		var val = getFieldVal(field, queryObj, Object.class);
		joinField = StringUtils.isBlank(joinCondition.value()) ? joinField : joinCondition.value();
		var joinFilter = join(joinField).onlyJoinObj();
		filter.join(joinField, forQuery(joinFilter, val), joinCondition.joinType());
		return joinFilter;
	}

	private static <T> AbstractFilter<?> limit(AbstractFilter<?> filter, Field[] fields, T obj) {
		var field = Stream.of(fields).filter(x -> x.isAnnotationPresent(Limit.class)).findAny().orElse(null);
		if (field == null)
			return filter;
		var num = getFieldVal(field, obj, Number.class);
		if (num == null)
			return filter;
		filter.limit(num.intValue());
		return filter;
	}

	@SuppressWarnings({ "unchecked" })
	private static <T, R> R getFieldVal(Field field, Object obj, Class<R> clazz) {
		field.setAccessible(true);
		try {
			var val = field.get(obj);
			if (val == null)
				return null;
			if (!clazz.isAssignableFrom(val.getClass()))
				throw new JpaAmebaException("字段类型错误");
			return (R) val;
		} catch (IllegalArgumentException | IllegalAccessException e) {
			logger.error("获取字段错误", e);
			return null;
		}
	}
}
